package com.ccx.myinterpreter;

import java.util.ArrayList;
import java.util.List;

import com.ccx.myinterpreter.Expr.Binary;
import com.ccx.myinterpreter.Expr.Get;
import com.ccx.myinterpreter.Expr.Grouping;
import com.ccx.myinterpreter.Expr.Literal;
import com.ccx.myinterpreter.Expr.Unary;
import com.ccx.myinterpreter.Expr.Variable;
import com.ccx.myinterpreter.Stmt.FunDecl;

import static com.ccx.myinterpreter.TokenType.*;

public class Parser {
    private final List<Token> tokens;    // 输入的词位列表
    int current;

    public Parser(List<Token> tokens) {
        this.tokens = tokens;
        this.current = 0;
    }

    public List<Stmt> parse() {
        List<Stmt> stmts = new ArrayList<>();
        while (!isAtEnd()) {
            stmts.add(declaration());
        }
        return stmts;
    }

    private Stmt declaration() {
        if(match(VAR)) {
            return varDecl();
        } else if(match(FUN)) {
            return funDecl();
        } else if(match(CLASS)) {
            return classDecl();
        }
        return statement();
    }

    private Stmt varDecl() {
        Token identifier = advance();
        if(match(EQUAL)) {
            Expr expr = expression();
            consume(SEMICOLON, "expect ; after expression!");
            return new Stmt.VarDecl(identifier, expr);
        }
        return new Stmt.VarDecl(identifier, null);
    }

    private Stmt classDecl() {
        Token name = advance();
        Variable supperClass = null;
        if(match(LESS)) {
            if(peek().getType() != IDENTIFIER)
                throw new RuntimeException("expect identifier after <!");
            supperClass = new Variable(advance());
        }
        consume(LEFT_BRACE, "Expect { after class name!");
        List<FunDecl> methods = new ArrayList<>();
        while (!match(RIGHT_BRACE) && !isAtEnd()) {
            FunDecl method =  (FunDecl) funDecl();
            methods.add(method);
        }
        return new Stmt.ClassDecl(name, methods, supperClass);
    }

    private Stmt funDecl() {
        Token identifier = advance();
        consume(LEFT_PAREN, "expect ( after fun name!");
        List<Token> params = new ArrayList<>();
        while(!match(RIGHT_PAREN) && !isAtEnd()) {
            params.add(advance());
            if(!match(COMMA)) {
                consume(RIGHT_PAREN, "expect ) after params!");
                break;
            }
        }
        consume(LEFT_BRACE, "exepect { after )!");
        Stmt.Block body = (Stmt.Block) block();
        return new Stmt.FunDecl(identifier, params, body);
    }

    private Stmt statement() {
        if(match(PRINT)) {
            return printStmt();
        } else if(match(LEFT_BRACE)) {
            return block();
        } else if(match(IF)) {
            return ifStmt();
        } else if(match(WHILE)) {
            return whileStmt();
        } else if(match(FOR)) {
            return forStmt();
        } else if(match(RETURN)) {
            return returnStmt();
        }
        return expressionStmt();
    }

    private Stmt returnStmt() {
        Expr val = null;
        if(!match(SEMICOLON)) {
            val = expression();
        }
        consume(SEMICOLON, "expect ; after return expression!");
        return new Stmt.ReturnStmt(val);
    }

    private Stmt whileStmt() {
        consume(LEFT_PAREN, "expect ( after while!");
        Expr condition = expression();
        consume(RIGHT_PAREN, "expect ) after condition!");
        Stmt body = statement();
        return new Stmt.WhileStmt(condition, body);
    }

    private Stmt forStmt() {
        consume(LEFT_PAREN, "expect ( after for!");
        Stmt first = null;
        Expr second = null;
        Expr third = null;
        if(match(VAR)) {
            first = varDecl();
        } else {
            first = expressionStmt();
        }
        if(!match(SEMICOLON)) {
            second = expression();
            consume(SEMICOLON, "expect ; after second expression in for!");
        }
        if(!match(RIGHT_PAREN)) {
            third = expression();
            consume(RIGHT_PAREN, "expect ) after third expression in for!");
        }
        Stmt body = statement();
        return new Stmt.forStmt(first, second, third, body);
    }

    private Stmt ifStmt() {
        consume(LEFT_PAREN, "expect ( after if!");
        Expr expr = expression();
        consume(RIGHT_PAREN, "expect ) after if expression");
        Stmt thenStmt = statement();
        Stmt elseStmt = null;
        if(match(ELSE)) {
            elseStmt = statement();
        }
        return new Stmt.IfStmt(expr, thenStmt, elseStmt);
    }

    private Stmt block() {
        List<Stmt> stmts = new ArrayList<>();
        while (!check(RIGHT_BRACE) && !isAtEnd()) {
            stmts.add(declaration());
        }
        consume(RIGHT_BRACE, "expect } !");
        return new Stmt.Block(stmts);
    }

    private Stmt expressionStmt() {
        Expr expr = expression();
        consume(SEMICOLON, "expect ; after expression!");
        return new Stmt.Expression(expr);
    }

    private Stmt printStmt() {
        Expr expr = expression();
        consume(SEMICOLON, "expect ; after expression!");
        return new Stmt.Print(expr);
    }

    private Expr expression() {
        return assignment();
    }

    private Expr assignment() {
        Expr left = logicOr();
        if(match(EQUAL)) {
            if(left instanceof Variable) {
                Expr right = expression();
                return new Expr.Assignment(((Variable) left).name, right);
            } else if(left instanceof Get) {
                Expr right = expression();
                Get get = (Get) left;
                return new Expr.Set(get.object, get.name, right);
            }
            throw new RuntimeException("left of = must be variable");
        }
        return left;
    }

    private Expr logicOr() {
        Expr left = logicAnd();
        while (match(OR)) {
            Token operator = previous();
            Expr right = logicAnd();
            left = new Expr.Binary(operator, left, right);
        }
        return left;
    }

    private Expr logicAnd() {
        Expr left = equality();
        while (match(AND)) {
            Token operator = previous();
            Expr right = equality();
            left = new Expr.Binary(operator, left, right);
        }
        return left;
    }

    private Expr equality() {
        Expr left = comparison();
        while(match(BANG_EQUAL, EQUAL_EQUAL)) {
            left = new Expr.Binary(previous(), left, comparison());
        }
        return left;
    }

    private Expr comparison() {
        Expr left = term();
        while(match(LESS, GREATER, LESS_EQUAL, GREATER_EQUAL)) {
            left = new Expr.Binary(previous(), left, term());
        }
        return left;
    }

    private Expr term() {
        Expr left = factor();
        while(match(PLUS, MINUS)) {
            left = new Expr.Binary(previous(), left, factor());
        }
        return left;
    }

    // 乘法表达式
    private Expr factor() {
        Expr left = unary();
        while(match(STAR, SLASH)) {
            left = new Expr.Binary(previous(), left, unary());
        }
        return left;
    }

    // 一元表达式
    private Expr unary() {
        if(match(BANG, MINUS)) {
            return new Expr.Unary(previous(), unary());
        }
        return call();
    }

    private Expr call() {
        Expr expr = primary();
        while(true) {
            if(match(LEFT_PAREN)) {
                List<Expr> args = new ArrayList<>();
                while(!match(RIGHT_PAREN) && !isAtEnd()) {
                    args.add(expression());
                    if(!match(COMMA)) {
                        consume(RIGHT_PAREN, "expect ) after arguments!");
                        break;
                    }
                }
                expr = new Expr.Call(expr, args);
            } else if(match(DOT)) {
                Token name = advance();
                expr = new Expr.Get(expr, name);
            } else break;
        }
        return expr;
    }

    // 字面量
    private Expr primary() {
        if(match(TRUE)) return new Expr.Literal(true);
        if(match(FALSE)) return new Expr.Literal(false);
        if(match(NUMBER, STRING)) return new Expr.Literal(previous().getLiteral());
        if(match(NIL)) return new Expr.Literal(null);
        if(match(LEFT_PAREN)) {
            Expr expr = expression();
            consume(RIGHT_PAREN, "match right paren failed!");
            return new Expr.Grouping(expr);
        }
        if(match(IDENTIFIER, THIS)) return new Expr.Variable(previous());
        if(match(SUPER)) {
            consume(DOT, "expect . after super!");
            return new Expr.Super(advance());
        }
        throw new RuntimeException("match primary failed!");
    }

    private boolean isAtEnd() {
        return current >= tokens.size();
    }

    private Token previous() {
        return tokens.get(current - 1);
    }

    private Token peek() {
        if(isAtEnd()) return null;
        return tokens.get(current);
    }

    private Token advance() {
        return tokens.get(current++);
    }

    private void consume(TokenType type, String msg) {
        if(!isAtEnd() && peek().getType() == type) {
            advance();
            return;
        }
        throw new RuntimeException(msg);
    }

    private boolean match(TokenType... types) {
        if(isAtEnd()) return false;
        for (TokenType type : types) {
            if(peek().getType() == type) {
                advance();
                return true;
            }
        }
        return false;
    }

    private boolean check(TokenType type) {
        if(isAtEnd()) return false;
        return peek().getType() == type;
    }

    // 后续遍历
    // static class PostPrintVisitor implements Expr.Visitor {

    //     @Override
    //     public Object visitLiteral(Literal literal) {
    //         System.out.print(" " + literal.value + " ");
    //         return null;
    //     }

    //     @Override
    //     public Object visitGrouping(Grouping grouping) {
    //         grouping.expr.accept(this);
    //         return null;
    //     }

    //     @Override
    //     public Object visitUnary(Unary unary) {
    //         System.out.print(" ( " + unary.operator.getLexeme() + " ");
    //         unary.right.accept(this);
    //         System.out.print(" ) ");
    //         return null;
    //     }

    //     @Override
    //     public Object visitBinary(Binary binary) {
    //         System.out.print(" ( " + binary.operator.getLexeme() + " ");
    //         binary.left.accept(this);
    //         binary.right.accept(this);
    //         System.out.print(" ) ");
    //         return null;
    //     }

        
    // }

}
